package helma.framework.core;

import helma.framework.repository.Repository;
import helma.framework.repository.Resource;
import helma.objectmodel.db.DbMapping;
import helma.objectmodel.db.Relation;
import helma.scripting.rhino.RhinoCore;
import helma.scripting.rhino.RhinoEngine;
import helma.util.ResourceProperties;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Map;

import org.mozilla.javascript.ScriptableObject;

import at.fpmedv.eclipse.helma.core.CollectionTreeObject;
import at.fpmedv.eclipse.helma.core.CoreFunctionTreeObject;
import at.fpmedv.eclipse.helma.core.FunctionTreeObject;
import at.fpmedv.eclipse.helma.core.HelmaCorePlugin;
import at.fpmedv.eclipse.helma.core.ITreeObject;
import at.fpmedv.eclipse.helma.core.ITreeParent;
import at.fpmedv.eclipse.helma.core.MountPointTreeObject;
import at.fpmedv.eclipse.helma.core.PropertyTreeObject;
import at.fpmedv.eclipse.helma.core.SkinTreeObject;

public class PrototypeDecorator {
	private Prototype proto;
	
	public PrototypeDecorator(Prototype proto) {
		this.proto = proto;
	}

	public Application getApplication() {
	    return proto.getApplication();
	}
    
	public Iterator getCodeResources() {
    	return proto.getCodeResources();
    }
    
	public DbMapping getDbMapping() {
    	return proto.getDbMapping();
    }
    
	public String getLowerCaseName() {
    	return proto.getLowerCaseName();
    }
    
	public String getName() {
    	return proto.getName();
    }
	
	public Prototype getParentPrototype() {
		return proto.getParentPrototype();
	}
	
	public Repository[] getRepositories() {
		return proto.getRepositories();
	}
	
    long getRepositoryChecksum() {
    	return proto.getRepositoryChecksum();
    }
    
	public Resource[] getResources() {
		return proto.getResources();
	}
    
    public Map getScriptableSkinMap() {
    	return proto.getScriptableSkinMap();
    }
    
    public Map getScriptableSkinMap(Object[] skinpath) {
    	return proto.getScriptableSkinMap(skinpath);
    }
    
	public Skin getSkin(String skinName, String parentName, String subName)
    throws IOException {
    	return proto.getSkin(skinName, parentName, subName);
    }
	
    public Iterator<Resource> getSkinResources() {
    	return proto.getSkinResources();
    }
    
    public ResourceProperties getTypeProperties() {
    	return proto.getTypeProperties();
    }
    
    public final boolean isInstanceOf(String pname) {
    	return proto.isInstanceOf(pname);
    }
    
    public long lastCodeUpdate() {
    	return proto.lastCodeUpdate();
    }
    
    public String toString() {
    	return proto.toString();
    }
    
    public void checkForUpdates() {
    	proto.checkForUpdates();
    }
    
    // new functions
    
    
    /**
     * collects all file Resources going down the extend chain
     * 
     * @return List of file resources for this prototype
     */
    public ArrayList<Resource> getAllResources(boolean getInheritedResources) {
		checkForUpdates();
		ArrayList<Resource> result = new ArrayList<Resource>();
		Prototype parentPrototype = getParentPrototype();
		if (parentPrototype != null && getInheritedResources) {
			result = (new PrototypeDecorator(parentPrototype)).getAllResources();
		}
		
		// add js files
		for (Iterator<Resource> iterRes = getCodeResources(); iterRes.hasNext();) {
			result.add(iterRes.next());
		}
		
		// add skins
		for (Iterator<Resource> iterRes = getSkinResources(); iterRes.hasNext();) {
			result.add(iterRes.next());
		}
		
		// add properties files
		for (Iterator<Resource> iterRes = getTypeProperties().getResources(); iterRes.hasNext();) {
			result.add(iterRes.next());
		}

		return result;
    }

    /**
     * default behavior is to get the inherited Resources
     * 
     * @return List of file resources for this prototype including inherited Resources
     */
    public ArrayList<Resource> getAllResources() {
    	return getAllResources(true);
    }
    
    public ArrayList<ITreeObject> getFunctions(ITreeParent parentPrototypeTreeObject, boolean getInheritedFunctions) {
    	checkForUpdates();
    	ArrayList<ITreeObject> result = new ArrayList<ITreeObject>();
		Prototype parentPrototype = getParentPrototype();
		if (parentPrototype != null && getInheritedFunctions) {
			result = (new PrototypeDecorator(parentPrototype)).getFunctions(parentPrototypeTreeObject);
		}
		
		Application app = getApplication();
		RequestEvaluator rEvaluator = app.getEvaluator();
		RhinoEngine sEngine = (RhinoEngine) rEvaluator.getScriptingEngine();
		RhinoCore rCore = sEngine.getCore();
		// get functions of this Prototype
		try {
			sEngine.enterContext();
			// all script compiled resources
			Object[] prototypeProperties = ((ScriptableObject) rCore.getPrototype(getName())).getAllIds();
			for (Object id: prototypeProperties) {
				String propertyName = id.toString();
				for (Iterator<ITreeObject> iterFunctions2 = (new ArrayList<ITreeObject>(result)).iterator(); iterFunctions2.hasNext();) {
					ITreeObject function2 = iterFunctions2.next();
					if (propertyName.equals(function2.getName()))
						result.remove(function2);
				}
				result.add((ITreeObject) new CoreFunctionTreeObject(propertyName, parentPrototypeTreeObject));
			}
			Map prototypeScriptProperties = rCore.getPrototypeProperties(getName());
			// filter doublets functions
			for (Iterator iterResources = prototypeScriptProperties.keySet().iterator(); iterResources.hasNext();) {
				String propertyName = (String) iterResources.next();
				// loop over core functions and remove item if exists
				for (int i = 0; i < result.size(); i++) {
					ITreeObject item = result.get(i);
					if (propertyName.equals(item.getName())) {
						result.remove(item);
						result.add((ITreeObject) new FunctionTreeObject(propertyName, parentPrototypeTreeObject));
					}
				}
			}
				
			sEngine.exitContext();
		} catch(java.lang.RuntimeException t) {
			HelmaCorePlugin.getDefault().log("Error reading Functions of " + getName(), t);
		} catch (IOException e) {
			HelmaCorePlugin.getDefault().log("Error entering Context", e);
		}
		app.releaseEvaluator(rEvaluator);

		return result;
    }
    
    /**
     * default behavior is to get the inherited functions
     * 
     * @return List of file resources for this prototype including inherited functions
     */
    public ArrayList<ITreeObject> getFunctions(ITreeParent parentPrototypeTreeObject) {
    	return getFunctions(parentPrototypeTreeObject, true);
    }
    
	public ArrayList<ITreeObject> getSkins(ITreeParent parentPrototypeTreeObject, boolean getInheritedSkins) {
		checkForUpdates();
		ArrayList<ITreeObject> result = new ArrayList<ITreeObject>();
		Prototype parentPrototype = getParentPrototype();
		if (parentPrototype != null && getInheritedSkins) {
			result = (new PrototypeDecorator(parentPrototype)).getSkins(parentPrototypeTreeObject);
		}
		for (Iterator<Resource> iterSkins = proto.getSkinResources(); iterSkins.hasNext();) {
			Resource skinResource = iterSkins.next();
			// remove any previous found skins
			for (Iterator<ITreeObject> iterSkins2 = (new ArrayList<ITreeObject>(result)).iterator(); iterSkins2.hasNext();) {
				ITreeObject skin2 = iterSkins2.next();
				if (skinResource.getName().equals(skin2.getName()))
					result.remove(skin2);
			}
			result.add((ITreeObject) new SkinTreeObject(skinResource, parentPrototypeTreeObject));
		}
		
		return result;
	}

	/**
     * default behavior is to get the inherited skins
     * 
     * @return List of file resources for this prototype including inherited skins
     */
    public ArrayList<ITreeObject> getSkins(ITreeParent parentPrototypeTreeObject) {
    	return getSkins(parentPrototypeTreeObject, true);
    }

	public ArrayList<ITreeObject> getProperties(ITreeParent parentPrototypeTreeObject, boolean getInheritedProperties) {
		checkForUpdates();
		ArrayList<ITreeObject> result = new ArrayList<ITreeObject>();
		Prototype parentPrototype = getParentPrototype();
		if (parentPrototype != null && getInheritedProperties) {
			result = (new PrototypeDecorator(parentPrototype)).getProperties(parentPrototypeTreeObject);
		}
		DbMapping childrenMapping = proto.getDbMapping();
		Relation childrenRelation = childrenMapping.getSubnodeRelation();
		if (childrenRelation != null) {
			DbMapping childrenProto = childrenRelation.getTargetType();
			if (childrenProto != null) {
				result.add((ITreeObject) new CollectionTreeObject(
						"_children", childrenRelation, parentPrototypeTreeObject));
			}
		}

		String[] mappings = proto.getDbMapping().getPropertyNames();
		for (int i = 0; i < mappings.length; i++) {
			String propertyName = mappings[i];
			DbMapping protoMapping = proto.getDbMapping();
			Relation propertyRelation = protoMapping.getPropertyRelation(propertyName);
			// remove any previous found skins
			for (Iterator<ITreeObject> iterProperties2 = (new ArrayList<ITreeObject>(result)).iterator(); iterProperties2.hasNext();) {
				ITreeObject property2 = iterProperties2.next();
				if (propertyName.equals(property2.getName()))
					result.remove(property2);
			}

			// check for collection
			if (propertyRelation.isVirtual() && propertyRelation.getPropertyMapping() != null) {
				result.add((ITreeObject) new MountPointTreeObject(
						propertyName, propertyRelation, parentPrototypeTreeObject));
			} else if (propertyRelation.isVirtual()) {
				result.add((ITreeObject) new CollectionTreeObject(
						propertyName, propertyRelation, parentPrototypeTreeObject));
			} else {
				result.add((ITreeObject) new PropertyTreeObject(
						propertyName, propertyRelation, parentPrototypeTreeObject));
			}
		}
		
		// TODO filter doublets
		return result;
	}

	/**
     * default behavior is to get the inherited properties
     * 
     * @return List of file resources for this prototype including inherited properties
     */
    public ArrayList<ITreeObject> getProperties(ITreeParent parentPrototypeTreeObject) {
    	return getProperties(parentPrototypeTreeObject, true);
    }

}
